package emulator.assembler;

import java.io.PrintStream;

import emulator.hardware.CPU;
import emulator.hardware.HwByte;
import emulator.hardware.HwWord;
import emulator.hardware.nmos6502.Command;
import emulator.hardware.nmos6502.Operand;
import emulator.support.OperandEval;
import emulator.util.ByteHelper;
import emulator.util.MemoryPtr;

public class Instruction {
	private byte[] raw_bytes;
	private String opcode;
	private String operand;
	private OperandEval	eval;
	
	public Instruction(MemoryPtr mem_ref, Command command, Operand operand, SymbolTable symbols)
	{
		opcode = command.getName();
		raw_bytes = new byte[1+operand.getByteCount()];
		mem_ref.get(raw_bytes);
		this.operand = operand.getString(mem_ref, symbols);
		eval = operand.getEvaluator(mem_ref);
	}
	
	public Instruction(MemoryPtr mem_ref, int data_length)
	{
		raw_bytes = new byte[data_length];
		mem_ref.get(raw_bytes);
		opcode = ".by";
		operand = getBytes();
	}
	
	/**
	 * @return the raw_bytes
	 */
	public String getBytes() {
		String result = new HwByte(raw_bytes[0]).toString();
		for (int i = 1; i < raw_bytes.length; i++)
			result += " " + new HwByte(raw_bytes[i]).toString();
		return result;
	}

	/**
	 * @return the raw_bytes
	 */
	public String getBytesShort() {
		String result = new HwByte(raw_bytes[0]).toString();
		int max_i = (raw_bytes.length > 3 ? 2 : raw_bytes.length); 
		for (int i = 1; i < max_i; i++)
			result += " " + new HwByte(raw_bytes[i]).toString();
		if (max_i < raw_bytes.length)
			result += "...";
		return result;
	}

	/**
	 * @return the opcode
	 */
	public String getOpcode() {
		return opcode;
	}

	/**
	 * @return the operand
	 */
	public String getOperand() {
		return operand;
	}

	public int getByteCount() {
		return raw_bytes.length;
	}

	public boolean isEnd() {
		return	opcode.equals("RTS") ||
				opcode.equals("JMP") ||
				opcode.equals("RTI") ||
				opcode.equals("BRK") ||
				opcode.equals("ILL");
	}

	public String getOperandDetails(CPU cpu, byte[] memory) {
		if (opcode.equals(".by"))
			return null;
		return eval.getDetails(cpu.getCpuState());
	}

	public boolean isAddressOperand() {
		return eval.isAddress();
	}
	
	public boolean isImmediate() {
		return operand.startsWith("#");
	}

	public boolean isStore() {
		return	opcode.equals("STA") ||
				opcode.equals("STX") ||
				opcode.equals("STY");
	}

	public boolean isLoad() {
		return	opcode.equals("LDA") ||
				opcode.equals("LDX") ||
				opcode.equals("LDY");
	}

	public char getStoreLoadReg()
	{
		return opcode.charAt(2);
	}

	public boolean isCompare() {
		return	opcode.equals("CMP") ||
				opcode.equals("CPX") ||
				opcode.equals("CPY");
	}
	
	public int getOperandAddress(CPU cpu) {
		if (isImmediate())
			return cpu.getPC()+1;
		return eval.getAddress(cpu.getCpuState());
	}

	public boolean isIndirectOperand() {
		return eval.isIndirect();
	}

	public int getOperandValue() {
		int result = -1;
		if (raw_bytes.length > 1)
		{
			result = ByteHelper.byteToInt(raw_bytes[1]);
			if (raw_bytes.length > 2)
				result += ByteHelper.byteToInt(raw_bytes[2]) << 8;
		}
		return result;
	}

	public void print(PrintStream out, String prefix, int address) {
		out.print(print(prefix, address));
	}

	public String print(String prefix, int address) {
		return prefix+"$" + new HwWord(address) + String.format(": %-10s ", getBytes()) + getOpcode() + " " + getOperand();
	}

	public boolean isTransfer() {
		return opcode.startsWith("T");
	}

	public Character getTargetReg() {
		return opcode.charAt(2);
	}

	public Character getSourceReg() {
		return opcode.charAt(1);
	}

	public Character getCompareReg() {
		return (getTargetReg() == 'P' ? 'A' : getTargetReg());
	}
}
